package network

import (
	"net"
	"sync"
	"sync/atomic"
)

//  接受发送的数据单元为message
type ConnectionCallBack interface {
	OnReadMsg(*Connection) (Message, error)
	OnConnection(*Connection)
	OnMessage(*Connection, Message)
	OnClose(*Connection)
}

type Message interface {
	Msg() []byte
}

type Connection struct {
	Conn        net.Conn
	recvBuff    chan Message
	sendBuff    chan Message
	closeNotify chan struct{}

	connCallBack    ConnectionCallBack
	ownerExitNotify chan struct{}

	closeOnce sync.Mutex
	isClosed  int32
}

func NewConnection(conn net.Conn, connCallBack ConnectionCallBack, exitNotify chan struct{}) *Connection {
	return &Connection{
		Conn:            conn,
		recvBuff:        make(chan Message, 10),
		sendBuff:        make(chan Message, 10),
		closeNotify:     make(chan struct{}),
		connCallBack:    connCallBack,
		ownerExitNotify: exitNotify,
	}
}

func (self *Connection) readMsgLoop() {
	defer self.Close()
	for {
		select {
		case <-self.closeNotify:
			return
		case <-self.ownerExitNotify:
			return
		default:
		}
		packet, err := self.connCallBack.OnReadMsg(self)
		if err != nil {
			return
		}
		self.connCallBack.OnMessage(self, packet)
	}
}

func (self *Connection) writeMsgLoop() {
	for {
		select {
		case <-self.closeNotify:
			return
		case <-self.ownerExitNotify:
			return
		case packet := <-self.sendBuff:
			_, err := self.Conn.Write(packet.Msg())
			if err != nil {
				self.Close()
				return
			}
		}
	}
}

func (self *Connection) SendPacket(packet Message) {
	if atomic.LoadInt32(&self.isClosed) == 0 {
		self.sendBuff <- packet
	}
}

func (self *Connection) Close() {
	self.closeOnce.Lock()
	defer self.closeOnce.Unlock()
	if atomic.LoadInt32(&self.isClosed) == 0 {
		atomic.StoreInt32(&self.isClosed, 1)
		self.connCallBack.OnClose(self)
		close(self.closeNotify)
		self.Conn.Close()
	}
}

func (self *Connection) Work(wg *sync.WaitGroup) {
	self.connCallBack.OnConnection(self)
	wg.Add(1)
	go func() {
		self.readMsgLoop()
		wg.Done()
	}()

	wg.Add(1)
	go func() {
		self.writeMsgLoop()
		wg.Done()
	}()
}
